package org.dicr.traffic.aggregator;

import java.util.*;

import org.dicr.traffic.source.*;
import org.dicr.util.net.*;

/**
 * Prefix Aggregator
 * 
 * @author <A href='http://dicr.org'>Igor A Tarasov</A>
 * @version 060705
 */
public class PrefixAggregator extends AbstractTrafficAggregator {

	/** Networks, in which prefixes will not be aggregated */
	@SuppressWarnings("unused")
	private Collection<Net> exclusives = new ArrayList<Net>();

	/**
	 * Constructor
	 */
	public PrefixAggregator() {
		super();
	}

	/**
	 * Constructor
	 * 
	 * @param sources
	 *            sources to aggregate from
	 * @param exclusiveNetworks
	 *            networks to presev prefixes from aggregation
	 */
	public PrefixAggregator(Collection<TrafficSource> sources, Collection<Net> exclusiveNetworks) {
		super();
		this.setSources(sources);
		this.setExclusiveNetworks(exclusiveNetworks);
	}

	/**
	 * Set exclusive networks.
	 * 
	 * @param networks
	 *            networks, in which prefixes will preserved from aggregation.
	 */
	public void setExclusiveNetworks(Collection<Net> networks) {
		if (networks == null) throw new IllegalArgumentException("null networks");
		synchronized (this.exclusives) {
			this.exclusives = new ArrayList<>(networks);
		}
	}

	/**
	 * Return exclusive networks.
	 * 
	 * @return networks, in which prefixes are preserved and not aggregated
	 */
	public Collection<Net> getExclusiveNetworks() {
		synchronized (this.exclusives) {
			return new ArrayList<>(this.exclusives);
		}
	}

	/**
	 * Add network to exclusives
	 * 
	 * @param net
	 *            network to add
	 */
	public void addExclusiveNetwork(Net net) {
		if (net == null) throw new IllegalArgumentException("null network");
		if (!this.exclusives.contains(net)) this.exclusives.add(net);
	}

	/**
	 * Create traffic element for aggregation
	 * 
	 * @param source
	 *            source element
	 * @param newSrc
	 *            new src
	 * @param newDst
	 *            new dst
	 * @return created element for aggregation
	 */
	protected static TrafficElement cloneForAggregation(TrafficElement source, IP newSrc, IP newDst) {
		if (source == null) throw new IllegalArgumentException("null source element");
		TrafficElement ret = new TrafficElement();
		ret.setTime(source.getTime());
		ret.setRouter(source.getRouter());
		ret.setSrc(newSrc != null ? newSrc : IP.ZERO);
		ret.setDst(newDst != null ? newDst : IP.ZERO);
		ret.setBytes(source.getBytes());
		return ret;
	}

	/**
	 * Do aggregation.
	 * 
	 * @param theBuffer
	 *            buffer to aggregate to
	 * @param element
	 *            traffic element, which aggregate to buffer
	 * @see org.dicr.traffic.aggregator.AbstractTrafficAggregator#aggregateElement(java.util.List,
	 *      org.dicr.traffic.source.TrafficElement)
	 */
	@Override
	protected void aggregateElement(List<TrafficElement> theBuffer, TrafficElement element) {
		if (theBuffer == null) throw new IllegalArgumentException("null buffer");
		if (element == null) throw new IllegalArgumentException("null element");

		// prepare prefixes
		IP src = element.getSrc();
		IP dst = element.getDst();
		if (src != null && src.equals(IP.ZERO)) src = null;
		if (dst != null && dst.equals(IP.ZERO)) dst = null;

		// nothing to do if prefixes empty
		if (src == null && dst == null) return;

		// reset not exclusive src and dst
		boolean srcIncluded = false;
		boolean dstIncluded = false;
		synchronized (this.exclusives) {
			for (Net net : this.exclusives) {
				if (src != null && !srcIncluded && net.isInclude(src)) srcIncluded = true;
				if (dst != null && !dstIncluded && net.isInclude(dst)) dstIncluded = true;
			}
		}

		// nothing to do if no exclusive prefixes
		if (!srcIncluded) src = null;
		if (!dstIncluded) dst = null;
		if (src == null && dst == null) return;

		// try to aggregate src and dst
		for (TrafficElement bel : theBuffer) {
			if (!bel.getSrc().equals(IP.ZERO)) {
				// aggregate and reset to null src
				if (src != null && src.equals(bel.getSrc())) {
					bel.setBytes(bel.getBytes() + element.getBytes());
					src = null;
				}
			} else {
				// aggregate and reset to null dst
				if (dst != null && dst.equals(bel.getDst())) {
					bel.setBytes(bel.getBytes() + element.getBytes());
					dst = null;
				}
			}
			// end if alrady aggregated
			if (src == null && dst == null) break;
		}

		// add to buffer if not aggregated
		if (src != null) theBuffer.add(cloneForAggregation(element, src, IP.ZERO));
		if (dst != null) theBuffer.add(cloneForAggregation(element, IP.ZERO, dst));
	}

}
